package ms.jacrim.tropos;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class Diagram {
	private static final String NOTATION_HEADER = "\t<notation:Diagram xmi:id=\"%s\" type=\"Tropos\" element=\"%s\" name=\"%s\" measurementUnit=\"Pixel\">\r\n";
	private static final String XMI_HEADER = "<xmi:XMI xmi:version=\"2.0\" xmlns:xmi=\"http://www.omg.org/XMI\" xmlns:cust=\"http://www.custom-style.org\" xmlns:notation=\"http://www.eclipse.org/gmf/runtime/1.0.0/notation\" xmlns:tr=\"http://www.troposproject.org\">\r\n";
	
	private Map<String, TroposObject> objects = new HashMap<String, TroposObject>();
	private Map<String, Service> services = new HashMap<String, Service>();

	private String id;
	private UID notationID;

	public Diagram() {
		id = Integer.toHexString(System.identityHashCode(this));
		notationID = UID.generate();
	}

	public Diagram(Element node) throws Exception {
		id = node.getAttribute("xmi:id");
		NodeList objNodes = node.getElementsByTagName("object");
		for (int i = 0; i < objNodes.getLength(); i++) {
			TroposObject obj = TroposObject.createObjectFromElement(this, (Element) objNodes.item(i));
			if (obj != null)
				addObject(obj);
		}
	}

	public TroposObject addObject(TroposObject obj) {
		if (obj != null && obj.getDiagram() == this)
			objects.put(obj.getId(), obj);
		return obj;
	}

	void addService(Service srv) {
		services.put(srv.getId(), srv);
	}

	void removeService(Service srv) {
		services.remove(srv.getId());
	}

	public String getId() {
		return id;
	}

	public Collection<TroposObject> getObjects() {
		return objects.values();
	}
	
	public Collection<Service> getServices() {
		return services.values();
	}

	@SuppressWarnings("unchecked")
	public <T extends TroposObject> List<T> getObjects(Class<T> objClass) {
		ArrayList<T> result = new ArrayList<T>();
		for (TroposObject o : objects.values())
			if (objClass.isAssignableFrom(o.getClass()))
				result.add((T) o);
		return result;
	}

	/***
	 * Return an object or service which have a given id.
	 * 
	 * @param id
	 *            The unique id of the object being get.
	 * @return
	 */
	public TroposObject getObjectById(String id) {
		TroposObject obj = objects.get(id);
		if (obj == null)
			obj = services.get(id);
		return obj;
	}

	@SuppressWarnings("unchecked")
	public <T extends TroposObject> T getObjectByName(String name, boolean normalizedName) {
		T result = (T) getActor(name, normalizedName);

		if (result == null)
			result = (T) getService(name, normalizedName);
		return result;
	}

	public Actor getActor(String name, boolean normalizedName) {
		Actor result = null;
		for (TroposObject obj : objects.values())
			if (TroposObject.ACTOR.equalsIgnoreCase(obj.getType()) && compareName(name, obj, normalizedName)) {
				result = (Actor) obj;
				break;
			}
		return result;
	}

	private boolean compareName(String name, TroposObject obj, boolean normalizedName) {
		return (normalizedName ? obj.getNormalizeName() : obj.getName()).equalsIgnoreCase(name);
	}

	public Service getService(String name, boolean normalizedName) {
		Service result = null;
		for (Service s : services.values())
			if (compareName(name, s, normalizedName)) {
				result = s;
				break;
			}
		return result;
	}

	public Service createService(String name, Service.ServiceType type) {
		Service result = getService(name, false);
		if (result == null) {
			result = new Service(this);
			result.setName(name);
			result.setServiceType(type);
		}
		return result;
	}

	public Actor createActor(String name, Actor.ActorType type) {
		Actor result = getActor(name, false);
		if (result == null) {
			result = new Actor(this);
			result.setName(name);
			result.setActorType(type);
			addObject(result);
		}
		return result;
	}

	public static Diagram load(String filename) throws Exception {
		DocumentBuilderFactory docBuilderFactory;
		docBuilderFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();

		Document doc = docBuilder.parse(new File(filename));
		doc.getDocumentElement().normalize();
		NodeList diagrams = doc.getElementsByTagName("tr:Diagram");

		return diagrams.getLength() > 0 ? new Diagram((Element) diagrams.item(0)) : null;
	}

	@Override
	public String toString() {
		StringBuilder result = new StringBuilder();
		result.append(XMI_HEADER);
		// generate Diagram part.
		result.append(String.format("\t<tr:Diagram xmi:id=\"%s\">\r\n", id));
		for (TroposObject obj : objects.values())
			obj.toString(result, 0, 2);
		result.append("\t</tr:Diagram>\r\n");
		// generate Notation part
		result.append(String.format(NOTATION_HEADER, notationID.toString(), id, ""));
		
		for (TroposObject obj: objects.values())
			obj.toString(result, 1, 2);
		
		result.append("\t</notation:Diagram>\r\n");
		
		result.append("</xmi:XMI>\r\n");
		return result.toString();
	}

}
